﻿using System;
using System.Data.Common;
using System.Diagnostics;
using System.Threading;
using System.Threading.Tasks;
using Microsoft.EntityFrameworkCore.Diagnostics;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Logging;

namespace Common.Interceptors
{
    /// <summary>
    /// SQL日志拦截器
    /// </summary>
    public class SqlLoggingInterceptor : DbCommandInterceptor
    {
        private static readonly AsyncLocal<Stopwatch> ExecutionStopwatch = new AsyncLocal<Stopwatch>();

        private readonly ILogger<SqlLoggingInterceptor> _logger;

        private const string StopwatchKey = "SqlExecutionStopwatch";

        public SqlLoggingInterceptor(ILogger<SqlLoggingInterceptor> logger)
        {
            _logger = logger;
        }

        public override InterceptionResult<DbDataReader> ReaderExecuting(DbCommand command, CommandEventData eventData,
            InterceptionResult<DbDataReader> result)
        {
            ExecutionStopwatch.Value = Stopwatch.StartNew();
            return base.ReaderExecuting(command, eventData, result);
        }

        public override InterceptionResult<object> ScalarExecuting(DbCommand command, CommandEventData eventData,
            InterceptionResult<object> result)
        {
            ExecutionStopwatch.Value = Stopwatch.StartNew();
            return base.ScalarExecuting(command, eventData, result);
        }

        public override InterceptionResult<int> NonQueryExecuting(DbCommand command, CommandEventData eventData,
            InterceptionResult<int> result)
        {
            ExecutionStopwatch.Value = Stopwatch.StartNew();
            return base.NonQueryExecuting(command, eventData, result);
        }

        public override ValueTask<InterceptionResult<DbDataReader>> ReaderExecutingAsync(DbCommand command,
            CommandEventData eventData, InterceptionResult<DbDataReader> result,
            CancellationToken cancellationToken = new CancellationToken())
        {
            ExecutionStopwatch.Value = Stopwatch.StartNew();
            return base.ReaderExecutingAsync(command, eventData, result, cancellationToken);
        }

        public override ValueTask<InterceptionResult<object>> ScalarExecutingAsync(DbCommand command,
            CommandEventData eventData, InterceptionResult<object> result,
            CancellationToken cancellationToken = new CancellationToken())
        {
            ExecutionStopwatch.Value = Stopwatch.StartNew();
            return base.ScalarExecutingAsync(command, eventData, result, cancellationToken);
        }

        public override ValueTask<InterceptionResult<int>> NonQueryExecutingAsync(DbCommand command,
            CommandEventData eventData, InterceptionResult<int> result,
            CancellationToken cancellationToken = new CancellationToken())
        {
            ExecutionStopwatch.Value = Stopwatch.StartNew();
            return base.NonQueryExecutingAsync(command, eventData, result, cancellationToken);
        }

        public override DbDataReader ReaderExecuted(DbCommand command, CommandExecutedEventData eventData,
            DbDataReader result)
        {
            var executionResult = base.ReaderExecuted(command, eventData, result);
            LogSqlWithParameters(command);
            return executionResult;
        }

        public override ValueTask<DbDataReader> ReaderExecutedAsync(DbCommand command,
            CommandExecutedEventData eventData, DbDataReader result,
            CancellationToken cancellationToken = new CancellationToken())
        {
            var executionResult = base.ReaderExecutedAsync(command, eventData, result, cancellationToken);
            LogSqlWithParameters(command);
            return executionResult;
        }

        public override int NonQueryExecuted(DbCommand command, CommandExecutedEventData eventData, int result)
        {
            var executionResult = base.NonQueryExecuted(command, eventData, result);
            LogSqlWithParameters(command);
            return executionResult;
        }

        public override ValueTask<int> NonQueryExecutedAsync(DbCommand command, CommandExecutedEventData eventData,
            int result, CancellationToken cancellationToken = new CancellationToken())
        {
            var executionResult = base.NonQueryExecutedAsync(command, eventData, result, cancellationToken);
            LogSqlWithParameters(command);
            return executionResult;
        }

        public override object ScalarExecuted(DbCommand command, CommandExecutedEventData eventData, object result)
        {
            var executionResult = base.ScalarExecuted(command, eventData, result);
            LogSqlWithParameters(command);
            return executionResult;
        }

        public override ValueTask<object> ScalarExecutedAsync(DbCommand command, CommandExecutedEventData eventData,
            object result,
            CancellationToken cancellationToken = new CancellationToken())
        {
            var executionResult = base.ScalarExecutedAsync(command, eventData, result, cancellationToken);
            LogSqlWithParameters(command);
            return executionResult;
        }

        private void LogSqlWithParameters(DbCommand command)
        {
            var stopwatch = ExecutionStopwatch.Value;
            stopwatch.Stop();
            var timeSpan = stopwatch.Elapsed;
            ExecutionStopwatch.Value = null;
            var sql = command.CommandText.Replace(Environment.NewLine, " ").Trim();
            // 替换参数占位符为实际值
            foreach (DbParameter parameter in command.Parameters)
            {
                var value = parameter.Value == DBNull.Value
                    ? "NULL"
                    : parameter.DbType == System.Data.DbType.String
                        ? $"'{parameter.Value}'"
                        : parameter.Value.ToString();
                sql = sql.Replace(parameter.ParameterName, value);
            }

            if (timeSpan.TotalSeconds > 1)
                _logger.LogWarning("SQL执行耗时较长: {Elapsed} 秒 | SQL: {Sql}", timeSpan.TotalSeconds.ToString("F3"), sql);
            else
                _logger.LogInformation("SQL执行耗时: {Elapsed} 毫秒 | SQL: {Sql}", timeSpan.TotalMilliseconds.ToString("F1"),
                    sql);
        }
    }
}